<template>
  <div class="animated-number">
    <span class="number">{{animatedNumber}}</span>
  </div>
</template>

<script>
import TWEEN from '@tweenjs/tween.js';
/*
动画化的数字。
每当value属性值变化时，会显示滚动动画，并且会根据新旧值的差显示+N/-N动画。
*/
export default {
  props: {
    value: {
      type: Number,
    },
    precision: {
      type: Number,
      default: 0
    },
    delay: {
      type: Number,
      default: 0
    },
    duration: {
      type: Number,
      default: 1000
    },
    // 从第几次值变化开始（初始化也算1次）有滚动动画
    animationFromChangeCount: {
      type: Number,
      default: 2
    },
    // 从第几次值变化开始（初始化也算1次）有差别动画
    differenceAnimationFromChangeCount: {
      type: Number,
      default: 3,
    },
    differenceAnimationOffsetX: {
      type: Number,
      default: 0
    }
  },
  data() {
    this.changeCount = 0; // 记录this.value变化的次数
    return {
      animatedNumber: null
    }
  },
  watch: {
    value: {
      handler(newValue, oldValue) {
        this.changeCount++;
        // 判断 是否需要动画
        if (this.changeCount < this.animationFromChangeCount) {
          this.animatedNumber = (+newValue).toFixed(this.precision);
          return;
        }

        // 动画
        let tween = new TWEEN.Tween(this.$data)
          .delay(this.delay)
          .to({animatedNumber: newValue}, this.duration)
          .easing(TWEEN.Easing.Quadratic.Out)
          .onUpdate(values => {
            this.animatedNumber = values.animatedNumber.toFixed(this.precision);
          });

        let shouldInitDifferenceAnimation = this.changeCount >= this.differenceAnimationFromChangeCount;

        if (shouldInitDifferenceAnimation) {
          if (this._isMounted) {
            this.initDifferenceAnimation(newValue, oldValue);
            tween.start();
            this.animate();
          } else {
            this.$nextTick(() => {
              this.initDifferenceAnimation(newValue, oldValue);
              tween.start();
              this.animate();
            });
          }
        } else {
          tween.start();
          this.animate();
        }
      },
      immediate: true
    }
  },
  methods: {
    animate() {
      if (TWEEN.update()) {
        requestAnimationFrame(this.animate);
      }
    },
    initDifferenceAnimation(newValue, oldValue) {
      let diffValue = newValue - (oldValue || 0);

      let numberEl = this.$el.querySelector('.number');

      let spirit = document.createElement('div');
      spirit.classList.add('changed-spirit', diffValue > 0 ? 'increment' : 'decrement');
      spirit.innerText = (diffValue > 0 ? '+' : '-') + Math.abs(diffValue);
      spirit.style.font = getComputedStyle(numberEl).font;
      // 计算合适的纵轴偏移
      let top = absolutePosition(this.$el, 'offsetTop')
      // 计算合适的横轴偏移
      let left = absolutePosition(this.$el, 'offsetLeft') + numberEl.offsetWidth + 8 + this.differenceAnimationOffsetX;
      // 要算上数字变化的之后的宽度
      let newValueLength = (newValue.toFixed(this.precision) + '').length;
      let oldValueLength = oldValue == null ? 0 : (oldValue.toFixed(this.precision) + '').length;
      if (oldValueLength > 0) {
        left += (newValueLength - oldValueLength) * numberEl.offsetWidth / oldValueLength;
      }
      spirit.style.left = left + 'px';
      spirit.style.top = top + 'px';
      document.body.appendChild(spirit);

      new TWEEN.Tween({top, opacity: 1})
        .delay(this.delay)
        .to({top: diffValue > 0 ? top - 40 : top + 40, opacity: 0}, 1500)
        .easing(diffValue > 0 ? TWEEN.Easing.Cubic.Out : TWEEN.Easing.Cubic.In)
        .onUpdate(values => {
          spirit.style.top = Math.round(values.top) + 'px';
          spirit.style.opacity = values.opacity;
        })
        .onComplete(() => {
          document.body.removeChild(spirit);
        })
        .start();
    }
  }
}

function absolutePosition(el, prop){
  return el.offsetParent ? el[prop] + absolutePosition(el.offsetParent, prop) : el[prop];
}
function getComputedStyle(el) {
    return window.getComputedStyle ? window.getComputedStyle(el) : el.currentStyle;
}
</script>

<style scope>
.animated-number {
  display: inline-block;
}

.changed-spirit {
  position: absolute;
  z-index: 2;
  opacity: 0;
}

.increment {
  color: #67C23A;
}

.decrement {
  color: #F56C6C;
}
</style>